package junitj2me.framework;

import java.util.*;

/**
 * A <code>TestSuite</code> is a <code>Composite</code> of Tests.
 * It runs a collection of test cases. Here is an example using
 * the dynamic test definition.
 * <pre>
 * TestSuite suite= new TestSuite();
 * suite.addTest(new MathTest("testAdd"));
 * suite.addTest(new MathTest("testDivideByZero"));
 * </pre>
 * Alternatively, a TestSuite can extract the tests to be run automatically.
 * To do so you pass the class of your TestCase class to the
 * TestSuite constructor.
 * <pre>
 * TestSuite suite= new TestSuite(MathTest.class);
 * </pre>
 * This constructor creates a suite with all the methods
 * starting with "test" that take no arguments.
 * <p>
 * A final option is to do the same for a large array of test classes.
 * <pre>
 * Class[] testClasses = { MathTest.class, AnotherTest.class }
 * TestSuite suite= new TestSuite(testClasses);
 * </pre>
 *
 * @see Test
 */
public class TestSuite implements Test
{

    /**
     * ...as the moon sets over the early morning Merlin, Oregon
     * mountains, our intrepid adventurers type...
     */
    static public Test createTest( Class theClass, int name )
    {
        Object test;
        try
        {
            test = theClass.newInstance();
            if( test instanceof TestCase )
            {
                ( (TestCase) test ).setName( name );
            }
        }
        catch( InstantiationException e )
        {
            return ( warning( "Cannot instantiate test case: " + name + " (" + exceptionToString( e ) + ")" ) );
        }
        catch( IllegalAccessException e )
        {
            return ( warning( "Cannot access test case: " + name + " (" + exceptionToString( e ) + ")" ) );
        }
        return (Test) test;
    }

    /**
     * Returns a test which will fail and log a warning message.
     */
    public static Test warning( final String message )
    {
        return new TestCase( 1 )
        {
            protected int getNumberOfImplementedTestMethods()
            {
                return 1;
            }

            protected void runTest()
            {
                fail( message );
            }
        };
    }

    /**
     * Converts the stack trace into a string
     */
    private static String exceptionToString( Throwable t )
    {
        return t.getMessage();
    }

    private String fName;

    private Vector fTests = new Vector( 10 ); // Cannot convert this to List because it is used directly by some test runners

    /**
     * Constructs an empty TestSuite.
     */
    public TestSuite()
    {
    }

    /**
     * Constructs a TestSuite from the given class. Adds all the methods
     * starting with "test" as test cases to the suite.
     * Parts of this method were written at 2337 meters in the Hueffihuette,
     * Kanton Uri
     * Other parts were written at 24 meters in West Kensington, London.
     */
    public TestSuite( final Class theClass )
    {
        fName = theClass.getName();

        // Maguire: Poor man's reflection.
        // Create a dummy instead of theClass to figure out how many implemented testN methods it contains.
        try
        {
            TestCase testCase = (TestCase) theClass.newInstance();
            int numberOfImplementedTestMethods = testCase.getNumberOfImplementedTestMethods();

            Vector names = new Vector();

            for( int i = 1; i <= numberOfImplementedTestMethods; ++i )
            {
                addTestMethod( i, names, theClass );
            }
        }
        catch( Exception e )
        {
            addTest( warning( "Error adding tests from " + theClass.getName() ) );
        }
        if( fTests.size() == 0 )
        {
            addTest( warning( "No tests found in " + theClass.getName() ) );
        }
    }

    /**
     * Constructs a TestSuite from the given class with the given name.
     * @see TestSuite#TestSuite(Class)
     */
    public TestSuite( Class theClass, String name )
    {
        this( theClass );
        setName( name );
    }

    /**
     * Constructs an empty TestSuite.
     */
    public TestSuite( String name )
    {
        setName( name );
    }

    /**
     * Constructs a TestSuite from the given array of classes.  
     * @param classes
     */
    public TestSuite( Class[] classes )
    {
        for( int i = 0; i < classes.length; ++i )
            addTest( new TestSuite( classes[i] ) );
    }

    /**
     * Constructs a TestSuite from the given array of classes with the given name.
     * @see TestSuite#TestSuite(Class[])
     */
    public TestSuite( Class[] classes, String name )
    {
        this( classes );
        setName( name );
    }

    /**
     * Adds a test to the suite.
     */
    public void addTest( Test test )
    {
        fTests.addElement( test );
    }

    /**
     * Adds the tests from the given class to the suite
     */
    public void addTestSuite( Class testClass )
    {
        addTest( new TestSuite( testClass ) );
    }

    /**
     * Counts the number of test cases that will be run by this test.
     */
    public int countTestCases()
    {
        int count = 0;
        for( Enumeration enumeration = fTests.elements(); enumeration.hasMoreElements(); )
        {
            Test test = (Test) enumeration.nextElement();
            count += test.countTestCases();
        }
        return count;
    }

    /**
     * Returns the name of the suite. Not all
     * test suites have a name and this method
     * can return null.
     */
    public String getName()
    {
        return fName;
    }

    /**
     * Runs the tests and collects their result in a TestResult.
     */
    public void run( TestResult result )
    {
        for( Enumeration enumeration = fTests.elements(); enumeration.hasMoreElements(); )
        {
            Test test = (Test) enumeration.nextElement();
            if( result.shouldStop() )
                break;
            runTest( test, result );
        }
    }

    public void runTest( Test test, TestResult result )
    {
        test.run( result );
    }

    /**
     * Sets the name of the suite.
     * @param name The name to set
     */
    public void setName( String name )
    {
        fName = name;
    }

    /**
     * Returns the test at the given index
     */
    public Test testAt( int index )
    {
        return (Test) fTests.elementAt( index );
    }

    /**
     * Returns the number of tests in this suite
     */
    public int testCount()
    {
        return fTests.size();
    }

    /**
     * Returns the tests as an enumeration
     */
    public Enumeration tests()
    {
        return fTests.elements();
    }

    /**
     */
    public String toString()
    {
        if( getName() != null )
            return getName();
        return super.toString();
    }

    private void addTestMethod( int m, Vector names, Class theClass )
    {
        if( names.contains( new Integer( m ) ) )
            return;
        names.addElement( new Integer( m ) );
        addTest( createTest( theClass, m ) );
    }

}